/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package practica1.manejoExperimento;

import beancomentarios.Comentario;
import beancomentarios.ModeloComentarios;
import es.miguelgonzalez.jgraficacomida.GraficaComidaIncorrectValueException;
import es.miguelgonzalez.jgraficacomida.ModeloGraficaComida;
import java.io.*;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import practica1.Practica1;
import practica1.language.Language;
import practica1.logic.LogicExperimento;
import practica1.logic.LogicPoblacion;
import practica1.ui.tablaPoblacion.ModeloPoblacionTabla;

/**
 * Clase que permite manejar un experimento en disco duro
 * @author Miguel González - Ceura
 */
public class ManejoExperimento {
    
    private static Document xmlDoc = null;
    private static Element elementoCabecera = null;
    
    /**
     * Método estático que permite abrir un experimento del disco duro
     * @param fichExperimento Fichero del experimento a abrir
     * @return LogicExperimento
     * @throws ExperimentoInvalidoException Lanza un error si no se pudo abrir
     * el experimento por tener un formato inválido
     */
    public static LogicExperimento abrirExperimento(File fichExperimento) 
            throws ExperimentoInvalidoException {
        String nombreExperimento = "";
        String tipoBacteria = "";
        String nombreInvestigador = "";
        String proyectoInvestigacion = "";
    
        
        //Construimos el DOM con las preferencias
        DocumentBuilderFactory dbFactory;
        DocumentBuilder docBuilder;
        
        LogicExperimento experimento = null;
        
        try
        {
            dbFactory = DocumentBuilderFactory.newInstance();
            docBuilder = dbFactory.newDocumentBuilder();
            
            if(fichExperimento.exists()) {
                xmlDoc  = docBuilder.parse(fichExperimento);
                elementoCabecera = xmlDoc.getDocumentElement();
            } else {
                Practica1.log.error("Abrir, no existe el experimento");
                throw new ExperimentoInvalidoException(
                        Language.getI().getP("NO_EXISTE_EXPERIMENTO"));
            }
            
            //Buscamos las propiedades del experimento
            NodeList lstPropiedades = elementoCabecera.
                    getElementsByTagName("propiedades-experimento");
            
            
            boolean propiedadesCorrecto = false;
            if(lstPropiedades.getLength() == 1) {
                Node nodePropiedades = lstPropiedades.item(0);
                
                //Obtenemos las propiedades del experimento
                NodeList lstValores = nodePropiedades.getChildNodes();
                
                //Recorremos las propiedades
                for(int i=0; i<lstValores.getLength(); i++) {
                    Node nodoPropExp = lstValores.item(i);
                    if(nodoPropExp.getNodeType() == Node.ELEMENT_NODE) {
                       
                        String nProp = nodoPropExp.getNodeName();

                        //Comprobamos en orden que se cumplen las propiedades esperadas
                        switch(i) {
                            case 1: //Nombre experimento
                                if(nProp.equals("nombre-experimento")) {
                                    nombreExperimento = nodoPropExp.
                                            getTextContent();
                                } else {
                                    Practica1.log.error("Abrir, nombre experimento inválido");
                                    throw new ExperimentoInvalidoException(
                                        Language.getI().getP("FICHERO_INVALIDO"));
                                }
                                break;
                            case 3:
                                if(nProp.equals("tipo-bacteria")) {
                                    tipoBacteria = nodoPropExp.
                                            getTextContent();
                                } else {
                                    Practica1.log.error("Abrir, tipo bacteria inválido");
                                    throw new ExperimentoInvalidoException(
                                        Language.getI().getP("FICHERO_INVALIDO"));
                                }
                                break;
                            case 5:
                                if(nProp.equals("nombre-investigador")) {
                                    nombreInvestigador = nodoPropExp.
                                            getTextContent();
                                } else {
                                    Practica1.log.error("Abrir, nombre investigador inválido");
                                    throw new ExperimentoInvalidoException(
                                        Language.getI().getP("FICHERO_INVALIDO"));
                                }
                                break;
                            case 7:
                                if(nProp.equals("proyecto-investigacion")) {
                                    proyectoInvestigacion = nodoPropExp.
                                            getTextContent();
                                    
                                    propiedadesCorrecto = true;
                                } else {
                                    Practica1.log.error("Abrir, proyecto investigación inválido");
                                    throw new ExperimentoInvalidoException(
                                        Language.getI().getP("FICHERO_INVALIDO"));
                                }
                                break;
                        }
                    }
                }
            } else {
                Practica1.log.error("Abrir, fichero inválido");
                throw new ExperimentoInvalidoException(
                        Language.getI().getP("FICHERO_INVALIDO"));
            }
            
            if(!propiedadesCorrecto) {
                throw new ExperimentoInvalidoException(
                        Language.getI().getP("FICHERO_INVALIDO"));
            }
            
            //Creamos el modelo del experimento
            experimento = new LogicExperimento(fichExperimento,
                    nombreExperimento, tipoBacteria, nombreInvestigador,
                    proyectoInvestigacion);
            
            //Buscamos las poblaciones del experimento
            NodeList lstPoblaciones = elementoCabecera.
                    getElementsByTagName("poblacion");
            
            //Recorremos las poblaciones y las analizamos
            for(int i=0; i<lstPoblaciones.getLength(); i++) {
                if(lstPoblaciones.item(i).getNodeType() == Node.ELEMENT_NODE) {
                    cargarPoblacion(experimento, lstPoblaciones.item(i));
                }
            }
        }
        catch (Exception ex)
        {
            Practica1.log.error(Language.getI().getP("ERROR"), ex);
            throw new ExperimentoInvalidoException(ex);
        }

        xmlDoc = null;
        elementoCabecera= null;
        experimento.setModified(false);
        return experimento;

    }
    
    /**
     * Carga los datos del XML de la población a un LogicPoblacion
     * @param exp LogicExperimento que contendrá la población
     * @param nPoblacion Nodo XML de la población
     * @throws ExperimentoInvalidoException Lanza un error si el experimento
     * es inváldio
     */
    private static void cargarPoblacion(LogicExperimento exp, Node nPoblacion)
            throws ExperimentoInvalidoException {
        String nombre = "";
        int tamanio = 0;
        int temperatura = 0;
        String escala = "";
        String fecha = "";
        String luminosidad = "";
        boolean sincronizar = true;
        ModeloGraficaComida modeloGraficaComida = null;
        ModeloPoblacionTabla modeloPoblacionTabla = null;
        ModeloComentarios modeloComentarios = null;
        
        //Obtenemos las propiedades de la población
        NodeList lstValores = nPoblacion.getChildNodes();
        
        
        String[] propPoblacion = new String[]{"nombre", "tamaño", "temperatura",
            "escala-temperatura", "fecha", "luminosidad", "sincronizar",
            "alimentacion", "tabla-datos", "comentarios"};
        
        
        boolean xmlValido = true;
        if(lstValores.getLength() >= 17) {
            for(int i=1; i<=17; i+= 2) {
                if(!lstValores.item(i).getNodeName().equals(propPoblacion[(int)i/2])) {
                    xmlValido = false;
                }
            }
        }
        
        if(xmlValido) {
            nombre = lstValores.item(1).getTextContent();
            tamanio = Integer.parseInt(lstValores.item(3).getTextContent());
            temperatura = Integer.parseInt(lstValores.item(5).getTextContent());
            escala = lstValores.item(7).getTextContent();
            fecha = lstValores.item(9).getTextContent();
            luminosidad = lstValores.item(11).getTextContent();
            sincronizar = lstValores.item(13).getTextContent().equals("si");
            modeloGraficaComida = cargarAlimentacion(lstValores.item(15));
            modeloPoblacionTabla = cargarTablaDatos(lstValores.item(17));
            modeloComentarios = cargarComentarios(lstValores.item(19));
        } else {
            Practica1.log.error("Abrir, error cargar población");
            throw new ExperimentoInvalidoException(
                    Language.getI().getP("FICHERO_INVALIDO"));
        }
        
        //Creamos un modelo de población genérico
        LogicPoblacion modPoblacion = new LogicPoblacion("PobGenerica", exp);
        
        //Cargamos los datos
        modPoblacion.setNombrePoblacion(nombre);
        modPoblacion.setTamanioPoblacion(tamanio);
        modPoblacion.setTemperatura(temperatura);
        modPoblacion.setEscalaTemperatura(escala);
        modPoblacion.setFecha(fecha);
        modPoblacion.setLuminosidad(luminosidad);
        modPoblacion.setSincronizarComida(sincronizar);
        modPoblacion.setModeloGraficaComida(modeloGraficaComida);
        modPoblacion.setModeloPoblacionTabla(modeloPoblacionTabla);
        modPoblacion.setModeloComentarios(modeloComentarios);
        modPoblacion.setModified(false);
        
        //Añadimos el modelo de la población al experimento
        exp.addPoblacion(modPoblacion);
    }
    
    /**
     * Carga el  modelo de los comentarios de la población
     * @param nCom Nodo de los comentarios
     * @return ModeloComentarios de la población
     * @throws ExperimentoInvalidoException Lanza un error si el experimento
     * es inválido
     */
    private static ModeloComentarios cargarComentarios(Node nCom) 
            throws ExperimentoInvalidoException {
        ModeloComentarios modeloComentarios = new ModeloComentarios();
        
        //Obtenemos los comentarios
        NodeList lstComentarios = nCom.getChildNodes();
        
        for(int i=1; i<lstComentarios.getLength(); i = i + 2) {
            NodeList nComentario = lstComentarios.item(i).getChildNodes();
            
            String[] datosComentario = new String[]{"fecha", "texto"};
            
            boolean xmlValido = true;
            if(nComentario.getLength() >= 7) {
                for(int j=1; j<=3; j += 2) {
                    if(!nComentario.item(j).getNodeName().
                            equals(datosComentario[(int)j/2])) {
                        xmlValido = false;
                    }
                }
            }
        
            if(xmlValido) {
                String fecha = nComentario.item(1).getTextContent();
                String texto = nComentario.item(3).getTextContent();

                modeloComentarios.addComentario(new Comentario(texto, fecha));
            } else {
                Practica1.log.error("Abrir, error cargar comentarios");
                throw new ExperimentoInvalidoException(
                        Language.getI().getP("FICHERO_INVALIDO"));
            }
        }
        
        return modeloComentarios;
    }
    
    /**
     * Carga el modelo de la gŕafica de la población
     * @param nAlim Nodo XML de la alimentación de la gráfica
     * @return ModeloGraficaComida de la población
     * @throws ExperimentoInvalidoException Lanza un error si el experimento es
     * inválido
     */
    private static ModeloGraficaComida cargarAlimentacion(Node nAlim)
            throws ExperimentoInvalidoException {
        int aliI = 150;
        int aliM = 150;
        int dia = 15;
        int aliF = 150;
        
        //Obtenemos las propiedades de la alimentación
        NodeList lstValores = nAlim.getChildNodes();
        
        String[] datosModeloComida = new String[]{"alimentacion-inicial",
            "alimentacion-maxima", "alimentacion-dia", "alimentacion-final"};
        
        
        boolean xmlValido = true;
        if(lstValores.getLength() >= 7) {
            for(int i=1; i<=7; i += 2) {
                if(!lstValores.item(i).getNodeName().
                        equals(datosModeloComida[(int)i/2])) {
                    xmlValido = false;
                }
            }
        }
        
        if(xmlValido) {
            aliI = Integer.parseInt(lstValores.item(1).getTextContent());
            aliM = Integer.parseInt(lstValores.item(3).getTextContent());
            dia = Integer.parseInt(lstValores.item(5).getTextContent());
            aliF = Integer.parseInt(lstValores.item(7).getTextContent());
        } else {
            Practica1.log.error("Abrir, error cargar alimentación");
            throw new ExperimentoInvalidoException(
                    Language.getI().getP("FICHERO_INVALIDO"));
        }
        
        
        ModeloGraficaComida modeloGraficaComida = null;
        try {
            modeloGraficaComida = new ModeloGraficaComida(aliI, dia, aliM, aliF);
        } catch (GraficaComidaIncorrectValueException ex) {
            throw new ExperimentoInvalidoException(ex);
        }
        
        return modeloGraficaComida;
    }
    
    /**
     * Carga del modelo de la tabla de la población
     * @param nDat Nodo de los datos de la tabla
     * @return ModeloPoblacionTabla de la población
     * @throws ExperimentoInvalidoException Lanza un error si el experimento
     * es inválido
     */
    private static ModeloPoblacionTabla cargarTablaDatos(Node nDat)
            throws ExperimentoInvalidoException {
        //Obtenemos las filas de datos
        NodeList lstValores = nDat.getChildNodes();
        String [][] datos = new String[30][];
        String []cabeceras = new String[]{
            Language.getI().getP("DIA"),
            Language.getI().getP("TAMANIO_POBLACION"),
            Language.getI().getP("BACTERIAS_MUERTAS"),
            Language.getI().getP("TEMPERATURA"),
            Language.getI().getP("DOSIS_COMIDA")};
        if(lstValores.getLength() != 0) {     
            //Recorremos las filas
            int contDias = 0;
            for(int i=0; i<lstValores.getLength(); i++) {
                Node nodoDiaTabla = lstValores.item(i);
                if(nodoDiaTabla.getNodeType() == Node.ELEMENT_NODE) {
                
                    String nProp = nodoDiaTabla.getNodeName();

                    //Si está el día en orden correcto
                    if(nProp.equals("datos-dia-" + (contDias + 1))) {
                        datos[contDias] = cargarTablaDia(contDias + 1, nodoDiaTabla);
                        contDias++;
                    } else {
                        Practica1.log.error("Abrir, errror cargar tabla datos");
                        throw new ExperimentoInvalidoException(
                                Language.getI().getP("FICHERO_INVALIDO"));
                    }
                }
            }
        } else {
            throw new ExperimentoInvalidoException(
                    Language.getI().getP("FICHERO_INVALIDO"));
        }
        
        return new ModeloPoblacionTabla(datos, cabeceras);
    }
    
    /**
     * Carga una fila de la tabla
     * @param dia Número del día
     * @param nDia Nodo del día
     * @return String[] Datos de la fila
     * @throws ExperimentoInvalidoException Lanza un error si el experimento
     * es inválido
     */
    private static String[] cargarTablaDia(int dia, Node nDia) 
            throws ExperimentoInvalidoException {
        //Obtenemos las propiedades del día
        NodeList lstValores = nDia.getChildNodes();
        String tamanio = "";
        String bacterias = "";
        String temperatura = "";
        String comida = "";    
        
        String[] datosTabla = new String[]{"tamaño-poblacion", "bacterias-muertas",
        "temperatura", "cantidad-comida"};
        
        boolean xmlValido = true;
        if(lstValores.getLength() >= 7) {
            for(int i=1; i<=7; i += 2) {
                if(!lstValores.item(i).getNodeName().equals(datosTabla[(int)i/2])) {
                    xmlValido = false;
                }
            }
        }
        
        if(xmlValido) {
            tamanio = lstValores.item(1).getTextContent();
            bacterias = lstValores.item(3).getTextContent();
            temperatura = lstValores.item(5).getTextContent();
            comida = lstValores.item(7).getTextContent();
        } else {
            Practica1.log.error("Abrir, error cargar tabla día");
            throw new ExperimentoInvalidoException(
                    Language.getI().getP("FICHERO_INVALIDO"));
        }
       
        return new String[]{Integer.toString(dia), 
                tamanio, bacterias, temperatura, comida};
    }
    
    /**
     * Clase estática que permite guardar un experimento en memoria
     * @param experimento LogicExperimento a guardar
     * @throws ExperimentoInvalidoException Si hay un problema al guardar el
     * experimento se provoca el error
     */
    public static void guardarExperimento(LogicExperimento experimento) 
            throws ExperimentoInvalidoException {
        //Construimos el DOM con las preferencias
        DocumentBuilderFactory dbFactory;
        DocumentBuilder docBuilder;
        
        try
        {
            dbFactory = DocumentBuilderFactory.newInstance();
            docBuilder = dbFactory.newDocumentBuilder();
            
            xmlDoc = docBuilder.newDocument();
            
            //Creamos el elemento que solo contendrá la cabecera
            elementoCabecera = xmlDoc.createElement("experimento");
            xmlDoc.appendChild(elementoCabecera);
            
            //Agregamos las propiedades del experimento
            Element propiedades = xmlDoc.createElement("propiedades-experimento");
            elementoCabecera.appendChild(propiedades);
            
            
            Element nombreExp = xmlDoc.createElement("nombre-experimento");
            nombreExp.setTextContent(experimento.getNombreExperimento());
            propiedades.appendChild(nombreExp);

            Element tipoBacteria = xmlDoc.createElement("tipo-bacteria");
            tipoBacteria.setTextContent(experimento.getTipoBacteria());
            propiedades.appendChild(tipoBacteria);
            
            Element nombreInvestigador = xmlDoc.createElement("nombre-investigador");
            nombreInvestigador.setTextContent(experimento.getNombreInvestigador());
            propiedades.appendChild(nombreInvestigador);
            
            Element proyectoInvestigacion = xmlDoc.createElement("proyecto-investigacion");
            proyectoInvestigacion.setTextContent(experimento.getProyectoInvestigacion());
            propiedades.appendChild(proyectoInvestigacion);
            
            ArrayList<LogicPoblacion> poblaciones = 
                    experimento.getPoblaciones();
            
            //Guardamos las poblaciones
            for(LogicPoblacion poblacion : poblaciones) {
                poblacion.setModified(false);
                guardarXMLPoblacion(poblacion);
            }
            
            //Escribimos el XML en el fichero
            OutputStream out = null;
            OutputStreamWriter osw = null;
            StreamResult result;
            DOMSource source;
            Transformer transformer;
            TransformerFactory transformerFactory;

            try {
                transformerFactory = TransformerFactory.newInstance();
                transformer = transformerFactory.newTransformer();
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

                source = new DOMSource(xmlDoc);
                out = new FileOutputStream(
                        experimento.getFichExperimento(), false);
                osw = new OutputStreamWriter(out, "UTF-8");
                result = new StreamResult(osw);

                transformer.transform(source, result);
            } catch (TransformerException ex) {
                Practica1.log.error("Guardar, error");
                Practica1.log.error(Language.getI().getP("ERROR"), ex);
                throw new ExperimentoInvalidoException(ex);
            } catch(IOException ex) {
                Practica1.log.error("Guardar, error");
                Practica1.log.error(Language.getI().getP("ERROR"), ex);
                throw new ExperimentoInvalidoException(ex);
            } finally {
                try {
                    if(osw != null) {
                        osw.close();
                    }
                    if(out != null) {
                        out.close();
                    }
                } catch(IOException ex) {
                    Practica1.log.error("Guardar, error");
                    Practica1.log.error(Language.getI().getP("ERROR"), ex);
                    throw new ExperimentoInvalidoException(ex);
                }
            }
            experimento.setModified(false);
        } catch (Exception ex)
        {
            Practica1.log.error("Guardar, error");
            Practica1.log.error(Language.getI().getP("ERROR"), ex);
            throw new ExperimentoInvalidoException(ex);
        }
        
    }
    
    /**
     * Carga en el XML para ser guardado la población que se está procesando
     * @param poblacion LogicPoblacion a cargar en el XML
     */
    private static void guardarXMLPoblacion(LogicPoblacion poblacion) {
        //Agregamos una población al experimento
        Element elPob = xmlDoc.createElement("poblacion");
        elementoCabecera.appendChild(elPob);
        
        //Cargamos todas las propiedades de la población
        String nomP = poblacion.getNombrePoblacion();
        String tamP = Integer.toString(poblacion.getTamanioPoblacion());
        String temP = Integer.toString(poblacion.getTemperatura());
        String escP = poblacion.getEscalaTemperatura();
        String fecP = poblacion.getFecha();
        String lumP = poblacion.getLuminosidad();
        String sinP = poblacion.isSincronizarComida() ? "si" : "no";
        ModeloGraficaComida graP = poblacion.getModeloGraficaComida();
        ModeloPoblacionTabla tabP = poblacion.getModeloPoblacionTabla();
        
        Element nombre = xmlDoc.createElement("nombre");
        nombre.setTextContent(nomP);
        elPob.appendChild(nombre);
        
        Element tamanio = xmlDoc.createElement("tamaño");
        tamanio.setTextContent(tamP);
        elPob.appendChild(tamanio);
        
        Element temperatura = xmlDoc.createElement("temperatura");
        temperatura.setTextContent(temP);
        elPob.appendChild(temperatura);
        
        Element escalaT = xmlDoc.createElement("escala-temperatura");
        escalaT.setTextContent(escP);
        elPob.appendChild(escalaT);
        
        Element fecha = xmlDoc.createElement("fecha");
        fecha.setTextContent(fecP);
        elPob.appendChild(fecha);
        
        Element luminosidad = xmlDoc.createElement("luminosidad");
        luminosidad.setTextContent(lumP);
        elPob.appendChild(luminosidad);
        
        Element sincronizar = xmlDoc.createElement("sincronizar");
        sincronizar.setTextContent(sinP);
        elPob.appendChild(sincronizar);
        
        String graPAlimI = Integer.toString(graP.getAlimentoInicial());
        String graPAlimM = Integer.toString(graP.getAlimentoMax());
        String graPAlimD = Integer.toString(graP.getDiaMax());
        String graPAlimF = Integer.toString(graP.getAlimentoFinal());
        
        Element alimentacion = xmlDoc.createElement("alimentacion");
        elPob.appendChild(alimentacion);
        
        Element alimentoIni = xmlDoc.createElement("alimentacion-inicial");
        alimentoIni.setTextContent(graPAlimI);
        alimentacion.appendChild(alimentoIni);
        
        Element alimentoMax = xmlDoc.createElement("alimentacion-maxima");
        alimentoMax.setTextContent(graPAlimM);
        alimentacion.appendChild(alimentoMax);
        
        Element alimentoDia = xmlDoc.createElement("alimentacion-dia");
        alimentoDia.setTextContent(graPAlimD);
        alimentacion.appendChild(alimentoDia);
        
        Element alimentoFin = xmlDoc.createElement("alimentacion-final");
        alimentoFin.setTextContent(graPAlimF);
        alimentacion.appendChild(alimentoFin);
        
        
        Element tabla = xmlDoc.createElement("tabla-datos");
        elPob.appendChild(tabla);
        //Recorro las filas de la tabla
        for(int i=0; i < 30; i++) {
            Element diaDatos = xmlDoc.createElement("datos-dia-" + (i + 1));
            tabla.appendChild(diaDatos);
            
            Element tamanioPoblacionDia = xmlDoc.createElement("tamaño-poblacion");
            tamanioPoblacionDia.setTextContent((String)tabP.getValueAt(i, 1));
            diaDatos.appendChild(tamanioPoblacionDia);
            
            Element bacteriasMuertasDia = xmlDoc.createElement("bacterias-muertas");
            bacteriasMuertasDia.setTextContent((String)tabP.getValueAt(i, 2));
            diaDatos.appendChild(bacteriasMuertasDia);
            
            Element temperaturaDia = xmlDoc.createElement("temperatura");
            temperaturaDia.setTextContent((String)tabP.getValueAt(i, 3));
            diaDatos.appendChild(temperaturaDia);
            
            Element cantidadComida = xmlDoc.createElement("cantidad-comida");
            cantidadComida.setTextContent((String)tabP.getValueAt(i, 4));
            diaDatos.appendChild(cantidadComida);
        }
        
        Element comentarios = xmlDoc.createElement("comentarios");
        elPob.appendChild(comentarios);
        
        for(Comentario comentario : poblacion.getModeloComentarios().
                getComentarios()) {
            Element c = xmlDoc.createElement("comentario");
            comentarios.appendChild(c);
            
            Element fC = xmlDoc.createElement("fecha");
            fC.setTextContent(comentario.getFecha());
            c.appendChild(fC);
            
            Element tC = xmlDoc.createElement("texto");
            tC.setTextContent(comentario.getTexto());
            c.appendChild(tC);
        }
            
    }
}
